#include "Adsorption.h"
#include <algorithm>
#include <iostream>

void Adsorption::InitDistribution(AdsorptionData &data)
{
	for (LabelDistribution::iterator itMap(data.labelDistributions.begin()),
		itEnd(data.labelDistributions.end()); itMap != itEnd; itMap++)
	{
		ListLabel* listLabel = new ListLabel();
		data.finalLabelDistributions[itMap->first] = listLabel;

		for (ListLabel::iterator itLabel(itMap->second->begin()), itLabelEnd(itMap->second->end());
			itLabel != itLabelEnd; itLabel++)
		{
			listLabel->push_back(new Label(*(*itLabel)));
		}
	}
}

void Adsorption::CalculateDv(Node *v, ListEdge &neighbors, LabelDistribution &estimatedDist,
							 ListLabel &lstDv)
{
	double dTotalWeight(0.0);

	for (ListEdge::iterator itEdge(neighbors.begin()),
		itEdgeEnd(neighbors.end()); itEdge != itEdgeEnd; itEdge++)
	{
		Node *u = (*itEdge)->data;
		LabelDistribution::iterator itEstimatedDist = estimatedDist.find(u);

		if (itEstimatedDist == estimatedDist.end())
		{
			continue;
		}
				
		ListLabel *Y_u = itEstimatedDist->second;

		dTotalWeight += (*itEdge)->weight;

		for (ListLabel::iterator itLabel(Y_u->begin()), itLabelEnd(Y_u->end());
			itLabel != itLabelEnd; itLabel++)
		{
			ListLabel::iterator itDvLabel = 
				std::find_if(lstDv.begin(), lstDv.end(), LabelFinder((*itLabel)->data));
			if(itDvLabel == lstDv.end())
			{
				lstDv.push_back(new Label(std::string((*itLabel)->data), (*itLabel)->weight * (*itEdge)->weight));
			}
			else
			{
				(*itDvLabel)->weight += (*itEdge)->weight;
			}
		}	
	}

	// normalize
	for (ListLabel::iterator itLabel(lstDv.begin()), itLabelEnd(lstDv.end());
		itLabel != itLabelEnd; itLabel++)
	{
		(*itLabel)->weight /= dTotalWeight;
	}
}

void Adsorption::UpdateEstimatedDistribution(Node *v, ListLabel &Dv,
											 ListLabel &initY_v,
											 ListNumber &probs,
											 ListLabel &estimatedLabels)
{
	for (ListLabel::iterator itLabel(estimatedLabels.begin()), itLabelEnd(estimatedLabels.end());
		itLabel != itLabelEnd; itLabel++)
	{
		delete (*itLabel);
	}
	estimatedLabels.clear();

	for (ListLabel::iterator itDv(Dv.begin()), itDvEnd(Dv.end());
		itDv != itDvEnd; itDv++)
	{
		// continue
		double dWeight = (probs[0]) * ((*itDv)->weight);

		ListLabel::iterator itY_v = std::find_if(initY_v.begin(), 
			initY_v.end(), LabelFinder((*itDv)->data));
		if (itY_v != initY_v.end())
		{
			// inject
			dWeight += probs[1] * ((*itY_v)->weight);
		}
		if ((*itDv)->data == v->Id)
		{
			// abandon
			dWeight += probs[2];
		}
		estimatedLabels.push_back(new Label(std::string((*itDv)->data), dWeight));
	}
	std::sort(estimatedLabels.begin(), estimatedLabels.end(), LabelComparer());
	if (estimatedLabels.size() > 50)
	{
		for (int i = estimatedLabels.size() - 1; i >= 50; --i)
		{
			delete (estimatedLabels[i]);
		}
		estimatedLabels.resize(50, NULL);
	}
}


void Adsorption::ProcessGraph(Graph &graph, LabelDistribution &initDist,
							  MapNumber &probs, LabelDistribution &estimatedDist)
{
	for (Graph::iterator itNode(graph.begin()), itNodeEnd(graph.end());
		itNode != itNodeEnd; itNode++)
	{
		Node* v = itNode->first;
		ListLabel Dv;

		CalculateDv(v, *(itNode->second), estimatedDist, Dv);
		UpdateEstimatedDistribution(v, Dv, *(initDist[v]), 
			*(probs[v]), *(estimatedDist[v]));

		// delete all labels inside Dv
		for (ListLabel::iterator itDv(Dv.begin()), itDvEnd(Dv.end());
			itDv != itDvEnd; itDv++)
		{
			delete (*itDv);
		}
		Dv.clear();
	}
}

void Adsorption::SortLabelDist(LabelDistribution &distribution)
{
	LabelComparer comparer;
	for (LabelDistribution::iterator itGraph(distribution.begin()), itGraphEnd(distribution.end());
		itGraph != itGraphEnd; itGraph++)
	{
		std::sort(itGraph->second->begin(), itGraph->second->end(), comparer);
	}
}

void Adsorption::Process(AdsorptionData &data, int iLoopCnt)
{
	InitDistribution(data);
	std::cout << "Initialized done!" << std::endl;

	for (int iLoop = 0; iLoop < iLoopCnt; ++iLoop)
	{
		std::cout << "Begin step " << iLoop << " on Users." << std::endl;
		ProcessGraph(data.graphUsers, data.labelDistributions,
			data.probabilities, data.finalLabelDistributions);
		std::cout << "Begin step " << iLoop << " on Items." << std::endl;
		ProcessGraph(data.graphItems, data.labelDistributions,
			data.probabilities, data.finalLabelDistributions);
	}

	SortLabelDist(data.finalLabelDistributions);
}